// Copyright 2007 Google Inc.  All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.enterprise.connector.afyd;

import com.google.enterprise.connector.spi.RepositoryException;

import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.client.Query;
import com.google.gdata.data.BaseFeed;
import com.google.gdata.data.Feed;
import com.google.gdata.data.Entry;
import com.google.gdata.data.DateTime;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.NotModifiedException;

import junit.framework.TestCase;

import java.util.List;
import java.util.LinkedList;
import java.util.Collections;

/**
 * This class is a test case that verifies several properties of the
 * CalendarEntryProvider implementation, namely:
 * (1) the NotModifiedException from service.query() results in no entries
 * (2) entries are sorted properly
 * (3) checkpoint is used to skip entries from query result
 * (4) provider can read its own checkpoint
 * 
 * @author amsmith@google.com (Adam Smith)
 */
public class CalendarEntryProviderTest extends TestCase {
  
  public static String aCheckpoint = DateTime.now() + "!" + "whatever";
  
  public void testHandlesNotModifiedException()
  throws RepositoryException, AuthenticationException {
    RiggedCalendarService service = new RiggedCalendarService(null);
    CalendarEntryProvider provider = 
        new CalendarEntryProvider(service, "admin@example.com", "secret");
    List entries = provider.getOrderedEntriesForUser("somebody", aCheckpoint);
    assertEquals(0, entries.size());
  }
  
  public void testSortsEntriesProperly() 
  throws RepositoryException, AuthenticationException {
    List entries = new LinkedList();
    
    int id = 0;
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        Entry entry = new Entry();
        String dateString = "200" + i + "-01-01T00:00:00.000Z";
        entry.setUpdated(DateTime.parseDateTime(dateString));
        entry.setId("j=" + j + ", i=" + i);
        entries.add(entry);
        id++;
      }
    }
    
    Collections.reverse(entries);
    Collections.rotate(entries, entries.size() / 2);
    Collections.swap(entries, 0, entries.size() - 1);
    
    Feed riggedFeed = new Feed();
    riggedFeed.getEntries().addAll(entries);
    
    RiggedCalendarService service = new RiggedCalendarService(riggedFeed);
    
    CalendarEntryProvider provider = 
      new CalendarEntryProvider(service, "admin@example.com", "secret");
    List results = provider.getOrderedEntriesForUser("lolcat", aCheckpoint);
    
    for (int idx = 0; idx < entries.size() - 1; idx++) {
      Entry current = (Entry) results.get(idx);
      Entry next = (Entry) results.get(idx + 1);
      int updatedResult = current.getUpdated().compareTo(next.getUpdated());
      int idResult = current.getId().compareTo(next.getId());

      if (updatedResult == 0 && idResult > 0)
        fail("Id-order violation within single second.");
      
      if (updatedResult > 1)
        fail("Updated-order violation.");
    }
  }
  
  public void testSkipsUsingCheckpoint()
  throws RepositoryException, AuthenticationException {
    final int COUNT = 9;
    final int SKIP = 6;
    
    List entries = new LinkedList();
    DateTime sometime = DateTime.now();
    for (int idx = 0; idx < COUNT; idx++) {
      Entry entry = new Entry();
      entry.setUpdated(sometime);
      entry.setId("" + idx);
      entries.add(entry);
    }
    
    Feed riggedFeed = new Feed();
    riggedFeed.getEntries().addAll(entries);
    RiggedCalendarService service = new RiggedCalendarService(riggedFeed);
    CalendarEntryProvider provider = 
      new CalendarEntryProvider(service, "admin@example.com", "secret");
    
    String checkpoint = sometime.toString() + "!" + (SKIP - 1);
    List results = provider.getOrderedEntriesForUser("loltapir", checkpoint);
    assertEquals(COUNT - SKIP, results.size());
  }
  
  public void testParsesCheckpoints()
  throws RepositoryException, AuthenticationException {
    
    List entries = new LinkedList();
    Entry entry = new Entry();
    entry.setUpdated(DateTime.now());
    entry.setId("meow");
    entries.add(entry);
    
    Feed riggedFeed = new Feed();
    riggedFeed.getEntries().addAll(entries);
    RiggedCalendarService service = new RiggedCalendarService(riggedFeed);
    CalendarEntryProvider provider = 
      new CalendarEntryProvider(service, "admin@example.com", "secret");
    

    // In this test we are not interested in the results, just what exceptions
    // are thrown.
    List results = null;
    String checkpoint = null;
    
    try {
      results = provider.getOrderedEntriesForUser("somebody", null);
      fail("Should have thrown DidNotPassIfModifiedSinceException.");
    } catch (DidNotPassIfModifiedSinceException dnpimse) {
      // this is the desired behavior
    }
    
    String realCheckpoint = provider.getCheckpointForEntry(entry);
        
    try {
      results = provider.getOrderedEntriesForUser("somebody", realCheckpoint);
    } catch (DidNotPassIfModifiedSinceException dnpimse) {
      // checkpoint was generated by this provider, it should be able to parse
      fail(dnpimse.toString());
    }
    
    try {
      String brokenCheckpoint = "123hax" + "!" + entry.getId();
      results = provider.getOrderedEntriesForUser("somebody", brokenCheckpoint);
      fail("Should have thrown DidNotPassIfModifiedSinceException.");
    } catch (DidNotPassIfModifiedSinceException dnpimse) {
      // this is the desired behavior again
    } 
  }
  
  /**
   * This class hacks CalendarService to skip authentication as well as return
   * a fixed response to any query.
   */
  public static class RiggedCalendarService extends CalendarService {
    
    private BaseFeed riggedFeed;
    
    public RiggedCalendarService(BaseFeed riggedFeed) {
      super("Google-AfydConnector-0");
      this.riggedFeed = riggedFeed;
    }
    
    public void setUserCredentials(String email, String password) 
    throws AuthenticationException {
      if (email.equals("admin@example.com") && password.equals("secret")) {
        return;
      } else {
        throw new AuthenticationException("Failed rigged login.");
      }
    }
    
    public BaseFeed query(Query query, Class klass, DateTime ifModifiedSince)
    throws NotModifiedException {
      if (ifModifiedSince == null) {
        throw new DidNotPassIfModifiedSinceException();
      }
      if (riggedFeed == null) {
        throw new NotModifiedException();
      } else {
        return riggedFeed;
      }
    }
  }
  
  /**
   * This exception signals that no (a null) value was passed to the
   * ifModifiedSince parameter of query().  While this is just fine to do in
   * practice we catch it in a test above to make sure that the provider can
   * parse it's own checkpoints.
   */
  public static class DidNotPassIfModifiedSinceException 
  extends RuntimeException {
    // nothing special
  }
}
